package com.techpleiad.ieducation.dao.impl;

import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;

import com.techpleiad.ieducation.dao.IAbstractDao;

/**
 * The Class AbstractDaoSupport.
 *
 * @param <T> the generic type
 * @param <K> the key type
 * 
 */
public abstract class AbstractDaoSupport<T extends Serializable, K extends Serializable> implements IAbstractDao<T,K> {
	
	/*-------------------------------------------
	|    I N S T A N C E   V A R I A B L E S    |
	===========================================*/
	
	/** The clazz. */
	private Class<T> clazz;

	@Autowired
	private SessionFactory sessionFactory;

	/*--------------------------------------------
	|        P U B L I C   M E T H O D S         |
	============================================*/
	
	@Override
	@SuppressWarnings("unchecked")
	public K save(final T entity) throws DataAccessException {
		return (K)getCurrentSession().save(entity);
	}

	@Override
	public void update(final T entity) throws DataAccessException {
		getCurrentSession().update(entity);
	}

	@Override
	@SuppressWarnings("unchecked")
	public T merge(final T entity) throws DataAccessException {
		return (T)getCurrentSession().merge(entity);
	}

	@Override
	public void delete(final T entity) throws DataAccessException {
		getCurrentSession().delete(entity);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public List findByQuery(final String query) throws DataAccessException {
		return findByQuery(query, (Object[]) null);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public List findByQuery(final String query, final Object... values) throws DataAccessException {
		Query queryObject = getCurrentSession().createQuery(query);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject.list();
	}

	@Override
	public void saveOrUpdate(final T entity) throws DataAccessException {
		getCurrentSession().saveOrUpdate(entity);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findAll() throws DataAccessException {
		return getCurrentSession().createCriteria(clazz).list();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public List findByCriteria(final DetachedCriteria detachedCriteria) throws DataAccessException {
		return findByCriteria(detachedCriteria, -1, -1);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public List findByCriteria(final DetachedCriteria criteria, final int firstResult, final int maxResults) throws DataAccessException {
		Criteria executableCriteria = criteria.getExecutableCriteria(getCurrentSession());
		if (firstResult >= 0) {
			executableCriteria.setFirstResult(firstResult);
		}
		if (maxResults > 0) {
			executableCriteria.setMaxResults(maxResults);
		}
		return executableCriteria.list();
	}

	@Override
	public int bulkUpdate(final String queryString) throws DataAccessException {
		return bulkUpdate(queryString, (Object)null);
	}

	@Override
	public int bulkUpdate(final String queryString, final Object value) throws DataAccessException {
		return bulkUpdate(queryString, new Object[] {value});
	}

	@Override
	public int bulkUpdate(final String queryString, final Object... values) throws DataAccessException {
		Query queryObject = getCurrentSession().createQuery(queryString);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject.executeUpdate();
	}

	@Override
	public void saveOrUpdateAll(final Collection<T> entities) throws DataAccessException {
		for (T entity : entities) {
			saveOrUpdate(entity);
		}
	}
	
	@Override
	public void deleteAll(final Collection<T> entities) throws DataAccessException {
		for (T entity : entities) {
			delete(entity);
		}
	}
	
	/**
	 * Sets the clazz.
	 *
	 * @param clazzToSet the new clazz
	 */
	public void setClazz(final Class< T > clazzToSet){
		clazz = clazzToSet;
	}
	
	/**
	 * Sets the session factory.
	 *
	 * @param sessionFactory the sessionFactory to set
	 */
	public void setSessionFactory(final SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
	
	/*--------------------------------------------
	|    P R O T E C T E D   M E T H O D S       |
	============================================*/
	
	/**
	 * Gets the current session.
	 *
	 * @return the current session
	 */
	protected final Session getCurrentSession(){
		return sessionFactory.getCurrentSession();
	}
	
	/**
	 * Releases the given JDBC Statement and ignore any thrown exception.
	 * This is useful for typical finally blocks in manual JDBC code.
	 * @param stmt the JDBC Statement to close (may be <code>null</code>)
	 */
	protected void releaseStatement(final Statement statement) {
		if (statement == null) {
			return;
		}
		try {
			statement.close();
		}
		catch (SQLException e) {
			// ignore
		}
	}

	/**
	 * Releases the given JDBC ResultSet and ignore any thrown exception.
	 * This is useful for typical finally blocks in manual JDBC code.
	 * @param rs the JDBC ResultSet to close (may be <code>null</code>)
	 */
	protected void releaseResultSet(final ResultSet resultSet) {
		if (resultSet == null) {
			return;
		}
		try {
			resultSet.close();
		}
		catch (SQLException e) {
			// ignore
		}
	}
}
